using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace MyClever.Lib
{
    public class Condition
    {
        public int layer;
        public string command;
    }   
    
    public sealed class ValidationService
    {
        static readonly ValidationService instance = new ValidationService();
        
        private int maxLayer;
        private string value;
        private List<Condition> conditions;
        private Dictionary<string, string> errorMessages;

#region Properties
        public static ValidationService Instance
        {
            get
            {
                return instance;
            }
        }

        public string ErrorMessages
        {
            get
            {
                string errorMessage = "";
                if (this.errorMessages.Count > 0)
                {
                    errorMessage = "Validation fails:\n";
                    foreach (KeyValuePair<string, string> errorOn in this.errorMessages)
                    {
                        errorMessage += errorOn.Key + " - " + errorOn.Value + "\n";
                    }
                }
                return errorMessage;
            }
        }
#endregion

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static ValidationService()
        {
        }
        
        /// <summary>
        /// Constructor of the Class
        /// </summary>
        public ValidationService()
        {
            this.conditions = new List<Condition>();
            this.errorMessages = new Dictionary<string, string>();
        }
        
        public bool IsValid(string validationRule, string value)
        {
            this.conditions.Clear();
            this.errorMessages.Clear();
            this.maxLayer = 0;
            this.value = value;

            this.ParseValidationElements(validationRule);
            
            // process all operations from the highest layer to the smallest
            while (this.maxLayer > 0)
            {
                while (true == this.GetNextOperation()) { }
                this.maxLayer--;
            }
            Debug.Assert(this.conditions.Count == 1);
            return Convert.ToBoolean(this.conditions[0].command);
        }
        
        private void ParseValidationElements(string parseText)
        {
            int layerID = 0;
            string command = "";

            // while this algorithm depends on operations wich have two arguments and one operator only (for example: IsPath && Contains{csv}),
            // sometimes we need an operation with one argument only (for example: "IsPath")
            // so we use a little trick, by appending the following dummy argument: "||False".
            // This has no effect on the final validation-result itself, but ensure always a valid operation with two arguments and one operator.
            parseText = "(" + parseText + "||False)";
            
            parseText = parseText.Replace("&&", "&");
            parseText = parseText.Replace("||", "|");

            foreach (char charOn in parseText)
            {
                if ('(' != charOn && ')' != charOn && '&' != charOn && '|' != charOn)
                {
                    command += charOn;
                }
                else
                {
                    if (false == String.IsNullOrEmpty(command))
                    {
                        Condition condition = new Condition();
                        condition.command = command.Trim();
                        condition.layer = layerID;
                        this.conditions.Add(condition);
                    }

                    if (charOn != ')')
                    {
                        Condition condition = new Condition();
                        condition.command = charOn.ToString();
                        condition.layer = layerID;
                        this.conditions.Add(condition);
                    }

                    if ('(' == charOn)
                    {
                        layerID++;
                        this.maxLayer = layerID;
                    }
                    else if (')' == charOn)
                    {
                        layerID--;
                    }

                    command = "";
                }
            }
        }

        private bool GetNextOperation()
        {
            // process all operations backwards (from the last to the first one) 
            for (int elementID = this.conditions.Count - 1; elementID >= 0; elementID--)
            {
                // find the first argument with the expected layer
                if (this.maxLayer == this.conditions[elementID].layer)
                {
                    // generate a complete operation by assembling: leftArgument operator rightArgument
                    // ... and save the result of this operation.
                    bool isValid = this.Compare(this.conditions[elementID - 2], this.conditions[elementID], this.conditions[elementID - 1]);

                    // there are two options, where we must save the operation-result
                    // 1) if this operation is embedded in a bracket, we save the result in the bracket itself
                    if ("(" == this.conditions[elementID - 3].command)
                    {
                        this.conditions[elementID - 3].command = isValid.ToString();
                        this.conditions.RemoveRange(elementID - 2, 3);
                    }
                    // 2) if the result is the next rightArgument for the next operation, we save the result in the leftArgument 
                    else
                    {
                        this.conditions[elementID - 2].command = isValid.ToString();
                        this.conditions.RemoveRange(elementID - 1, 2);                        
                    }
                    return true;
                }
            }
            return false;
        }

        private bool Compare(Condition leftArgument, Condition rightArgument, Condition workingOperator)
        {
            bool isLeftArgumentValid = this.IsArgumentValid(leftArgument);
            bool isRightArgumentValid = this.IsArgumentValid(rightArgument);

            if (true == (workingOperator.command == "&"))
            {
                return (isLeftArgumentValid && isRightArgumentValid);
            }
            return (isLeftArgumentValid || isRightArgumentValid);
        }

        private bool IsArgumentValid(Condition argument)
        {
            bool isArgumentValid = false;
            if (false == Boolean.TryParse(argument.command, out isArgumentValid))
            {
                // get all necessary validation arguments.
                List<string> validationArgs;
                string validationMethodName = this.GetValidationArguments(argument.command, out validationArgs);

                Type validationType = Type.GetType("MyClever.Lib.Validation");
                MethodInfo validationMethodInfo = validationType.GetMethod(validationMethodName.ToLower());

                // start the validation by using .NET-Reflection
                try
                {
                    isArgumentValid = (bool)validationMethodInfo.Invoke(validationType, validationArgs.ToArray());
                }
                catch (Exception exception)
                {
                    this.errorMessages.Add(argument.command, exception.Message);
                    isArgumentValid = false;
                }
                finally
                {
                    validationMethodInfo = null;

                    if (false == isArgumentValid)
                    {
                        this.errorMessages.Add(argument.command, isArgumentValid.ToString());                        
                    }
                }
            }
            return isArgumentValid;
        }

        /// <summary>
        /// Gets all arguments the validation needed.
        /// You can set the validationType in your mission.xml-files by using 'ValidationType="ValditionTypeXYZ{ValidationArg1}"'.
        /// </summary>
        /// <param name="validation">The validation-rule which should check the conten-value.</param>
        /// <param name="value">The content-value you like to validate.</param>
        /// <param name="validationArgs">Holds all required validation arguments.</param>
        /// <returns>
        /// The name of the validation-method itself.
        /// </returns>
        private string GetValidationArguments(string validation, out List<string> validationArgs)
        {
            // extract all validation arguments.
            string[] validateMethodeElements = validation.Split(new char[] { '{', '}' });
            string validationMethodeName = validateMethodeElements[0];

            // set all validate-methode arguments ...
            validationArgs = new List<string>();
            // ... 1) the setting-parameter value, which we like to validate
            validationArgs.Add(this.value);
            // ... 2) all other arguments, which we need to validate 
            // (for example a string-character to comparing)
            for (int i = 1; i < validateMethodeElements.Length - 1; i++)
            {
                validationArgs.Add(validateMethodeElements[i]);
            }

            return validationMethodeName;
        }
    }
}
